/**
 * 
 */
package idv.takeshi.software.productline.bookstore.application.service.spring;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;


import idv.takeshi.software.productline.bookstore.application.service.MailService;
import idv.takeshi.software.productline.bookstore.application.service.UserAccountAlreadyActivatedException;
import idv.takeshi.software.productline.bookstore.application.service.UserAccountNotFoundException;
import idv.takeshi.software.productline.bookstore.application.service.UserRegistrationSerivce;
import idv.takeshi.software.productline.bookstore.domain.model.useraccount.Role;
import idv.takeshi.software.productline.bookstore.domain.model.useraccount.UserAccount;
import idv.takeshi.software.productline.bookstore.domain.model.useraccount.UserAccountDuplicateException;
import idv.takeshi.software.productline.bookstore.domain.model.useraccount.UserAccountRepository;

import org.apache.commons.lang.Validate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.dao.DataAccessException;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.transaction.annotation.Transactional;

/**
 * A abstract service implementation for UserRegistrationSerivce.
 * @author takeshi
 *
 */
@Transactional(rollbackFor = Exception.class)
public abstract class AbstractUserRegistrationSerivce implements UserRegistrationSerivce, UserDetailsService {
	
	@Autowired
	private UserAccountRepository userAccountRepository;
	@Autowired
	private PasswordEncoder passwordEncoder;
	@Autowired
	private MailService mailService;
	@Autowired
	private MessageSource messageSource;

	/* (non-Javadoc)
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	public UserDetails loadUserByUsername(String username)
			throws UsernameNotFoundException, DataAccessException {
		UserDetails user = null;
		UserAccount userAccount = this.getUserAccountByName(username);
		
		if(null == userAccount || !userAccount.isEnabled()) 
			throw new UsernameNotFoundException("userAccount not found for :" + username);
		
		List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
		List<Role> roles = userAccount.getRoles();
		for(Role role : roles){
			authorities.add(new GrantedAuthorityImpl(role.getRoleName()));
		}
		
		//create user for security mechanism use
		user = new User(userAccount.getAccountName(), 
				userAccount.getPassword(), 
				userAccount.isEnabled(), 
				userAccount.isNonExpired(), 
				userAccount.isCredentialsNonExpired(), 
				userAccount.isNonLocked(), authorities);
		
		return user;
	}

	/* (non-Javadoc)
	 * @see idv.takeshi.software.productline.bookstore.application.service.UserRegistrationSerivce#activateUserAccountByActivationKey(java.lang.String)
	 */
	public void activateUserAccountByActivationKey(String activationKey) throws UserAccountNotFoundException {
		Validate.notEmpty(activationKey, "activationKey shall always not be null/empty");
		UserAccount userAccount = this.userAccountRepository.findByActivationKey(activationKey);
		if(null == userAccount) 
			throw new UserAccountNotFoundException("userAccount not found for activationKey:" + activationKey);
		userAccount.setEnabled(true);
	}


	/* (non-Javadoc)
	 * @see idv.takeshi.software.productline.bookstore.application.service.UserRegistrationSerivce#getUserAccountByName(java.lang.String)
	 */
	@Transactional(readOnly = true)
	public UserAccount getUserAccountByName(String accountName) {
		return this.userAccountRepository.findByAccountName(accountName);
	}

	/* (non-Javadoc)
	 * @see idv.takeshi.software.productline.bookstore.application.service.UserRegistrationSerivce#addUserAccountAndSendMail(idv.takeshi.software.productline.bookstore.domain.model.useraccount.UserAccount, java.util.Locale, java.lang.String)
	 */
	public <T extends UserAccount> void addUserAccountAndSendMail(T userAccount, Locale locale, String hostUrl) throws 
		UserAccountAlreadyActivatedException, UserAccountDuplicateException {
		Validate.notNull(userAccount, "userAccount shall always not be null");
		Validate.notEmpty(userAccount.getOriginalPassword(), 
				"userAccount.OriginalPassword shall always not be null/empty");
		Validate.notEmpty(hostUrl, "hostUrl shall always not be null/empty");
		
		if(userAccount.isEnabled())
			throw new UserAccountAlreadyActivatedException("User:" + userAccount.getAccountName() +
					"'s account already activated");
		
		//encoding password
		String password = null;
		String encodedPassword = null;
		
		password = userAccount.getOriginalPassword();
		encodedPassword = this.passwordEncoder.encodePassword(password, null);
		userAccount.setPassword(encodedPassword);
		
		//save newly created user account
		this.userAccountRepository.add(userAccount);
		
		doSendMail(userAccount, locale, hostUrl);
	}

	/**
	 * @return the userAccountRepository
	 */
	public UserAccountRepository getUserAccountRepository() {
		return userAccountRepository;
	}

	/**
	 * @param userAccountRepository the userAccountRepository to set
	 */
	public void setUserAccountRepository(UserAccountRepository userAccountRepository) {
		this.userAccountRepository = userAccountRepository;
	}

	/**
	 * @return the passwordEncoder
	 */
	public PasswordEncoder getPasswordEncoder() {
		return passwordEncoder;
	}

	/**
	 * @param passwordEncoder the passwordEncoder to set
	 */
	public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
		this.passwordEncoder = passwordEncoder;
	}

	/**
	 * @return the mailService
	 */
	public MailService getMailService() {
		return mailService;
	}

	/**
	 * @param mailService the mailService to set
	 */
	public void setMailService(MailService mailService) {
		this.mailService = mailService;
	}

	/**
	 * @return the messageSource
	 */
	public MessageSource getMessageSource() {
		return messageSource;
	}

	/**
	 * @param messageSource the messageSource to set
	 */
	public void setMessageSource(MessageSource messageSource) {
		this.messageSource = messageSource;
	}

	/**
	 * A abstract method for it's sub-classes to implement corresponding send-mail logics,
	 * could be empty-implemented if no mail to send.
	 * @param <T>
	 * @param userAccount
	 * @param locale
	 * @param hostUrl
	 */
	protected abstract <T extends UserAccount> void doSendMail(T userAccount, Locale locale, String hostUrl);

}
